Introduction

This notebook details the creation of interactive maps using leaftlet and a data set of earthquakes in Missouri. We created the shapefiles referenced here in CreateQuakes.Rmd and CountyQuakes.Rmd.

Project Set Up

Before proceeding, we’ll make sure our notebook is set up to work with our project data structure.

knitr::opts_knit$set(root.dir = here::here())

See CreateQuakes.Rmd for an explanation of how this function works.

Dependencies

The following packages are required for this notebook:

library(dplyr)        # data wrangling
library(ggplot2)      # plotting
library(skimr)        # descriptive statistics
library(sf)           # spatial data tool
library(tigris)       # download census shapefiles
library(leaflet)      # interactive maps
library(htmltools)    # html tools for use with leaflet popups
library(ggthemes)     # ggplot2 theme for mapping

Load Earthquake Data

We’ll want to start by reading the earthquake data we created previously into R’s global environment. We can use the read_sf() function to accomplish this:

moQuakes <- read_sf("results/GEO_Earthquakes/GEO_Earthquakes.shp")
countyQuakes <- read_sf("results/GEO_EarthquakesByCounty/GEO_EarthquakesByCounty.shp")

leaflet returns warnings about data that do not use the World Geodetic System’s 1984 revision, known as WGS84. Like NAD1983, WGS84 is a geographic coordinate system designed for worldwide locating of data. We can transform both of our shapefiles, which were created using NAD1983:

moQuakes <- st_transform(moQuakes, crs = 4326)
countyQuakes <- st_transform(countyQuakes, crs = 4326)

Simple Leaflet Map

With our transformed data, we can make a simple leaflet map of earthquake locations in Missouri. Our pipeline requires three elements:

  1. We need to call the leaflet() functin and define the data source as moQuakes, then
  2. We need to add map tiles using addTiles(), then
  3. We need to project our point data using addMarkers()

The tiles refer to the basemap, which is sourced from OpenStreetMap by default.

leaflet(data = moQuakes) %>%
  addTiles() %>%
  addMarkers()

Changing the Basemap

For mapping detailed data, I find the OpenStreetMap basemap has too many labels and markers on it. I prefer a simpler basemap, and luckily leaflet has the ability to pick from dozens of options. We can specify our preference using addProviderTiles() instead of addTiles():

leaflet(data = moQuakes) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addMarkers()

You can learn more about basemaps in leaflet here, and see an interactive preview of the various choices here.

Working with Point Data

We’ll use the earthquake data to introduce a number of concepts for working with point data using leaflet.

Adding Pop-ups

One of the great things about leaflet is that pop-ups can be added to each marker to provide details about the point. For example, we could add information about the date and magnitude of each earthquake. We’ll paste a template together into an object in our enviornment that leaflet can use for each pop-up like so:

moQuakes <- mutate(moQuakes, date = substr(time, 1, 10))
earthquake_popup <- paste("Date: ", moQuakes$date,  "<br/>", 
                           "Magnitude: ", moQuakes$mag)

The <br/> tag is html that places a carriage return into the pop-up text. This will place the date and the magnitude on different lines.

We can insert the pop-up into our map by using the popup argument in addMarkers() and preceding earthquake_popup with a tilde (~). When you execute the chunk below, try clicking on different markers to reveal information about the earthquakes.

leaflet(data = moQuakes) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addMarkers(popup = ~earthquake_popup)

Customizing Makers

leaflet has a wide variety of tools for customizing markers. The code chunk below defines three points based on the magnitude of a given earthquake. If the earthquake has a magnitude of less than 3, a green marker will be used. If the magnitude is between 3 and 4, and orange marker will be used. Finally, for the few earthquakes with magnitudes greater than 4, the marker will be red. These points are defined inside a function that we can use as we construction our icons.

# define function to set a color based on magnitude
getColor <- function(quakeColor) {
  sapply(moQuakes$mag, function(mag) {
    if(mag < 3) {
      "green"
    } else if(mag < 4) {
      "orange"
    } else {
      "red"
    } 
  })
}

After creating a tool for defining marker colors, we can utiize it as part of the awesomeIcons() function call. We’ll use the perfectly named Font Awesome library for the marker image, and set the marker color equal to the result of the getColor() function we just wrote.

# define icon properties, including setting marker color based on the function we just write
icons <- awesomeIcons(
  library = 'fa',
  icon = 'map-marker-alt',
  markerColor = getColor(moQuakes)
)

Now that we have our icons constructed, we’ll apply them to an updated version of our map using addAwesomeMarkers() in lieu of add Markers():

# apply custom icons
leaflet(data = moQuakes) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addAwesomeMarkers(popup = ~earthquake_popup, icon = icons)

Clustering Points

The map above is difficult to read because there are so many points in southeast Missouri. One way to help readers is to cluster points. The clusterOptions argument can be included in addAwesomeMarkers() and set equal to markerClusterOptions(). This will allow leaflet to cluster points together based on proximety. The colors used by leaflet are very close to our marker colors from before, which is confusing. They do not have anything to do with the magnitude, however. Instead, they reflect the number of child points clustered under each marker (green for few, yellow for a moderate number, and redish-orange for a large number). Hovering your mouse over a custer will reval the boundaries of the included child points, and clicking on a cluster will zoom you in to those boundaries.

# apply custom icons
leaflet(data = moQuakes) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addAwesomeMarkers(
    popup = ~earthquake_popup, 
    icon = icons, 
    clusterOptions = markerClusterOptions()
  )

Working with Polygon Data

We can also use leaflet to map polygon data, like our count of earthquakes by county. We’ll start by defining pop-up content for counties that have had earthquakes. The process is similar to the process above - we’ll concatenate text (including the <br/> html tag) and save it in an object named county_popup.

county_popup <- paste(countyQuakes$NAMELSAD,  "<br/>", 
                      "Earthquakes Since 1973: ", countyQuakes$COUNT)

We’ll also define a color ramp for mapping and save it in an object called binpal. We’ll use three breaks and the Reds color ramp from RColorBrewer, just like we did in our static map created in CountyQuakes.Rmd.

binpal <- colorBin("Reds", countyQuakes$COUNT, 3, pretty = FALSE)

Once we have pop-ups and a color ramp created, we’ll apply them to a map that uses the addPolygons() function in lieu of addMarkers(). Within the addPolygons(), there are arguments for both the stroke and the fill of each polygon. We also add an additional lay of interactivity that highlights each selected polygon in white.

leaflet(data = countyQuakes) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(color = "#444444", 
              weight = 1, 
              smoothFactor = 0.5,
              opacity = 1.0, 
              fillOpacity = 0.5,
              fillColor = ~binpal(COUNT),
              popup = ~county_popup, 
              highlightOptions = highlightOptions(
                color = "white", 
                weight = 2,
                bringToFront = TRUE)
              )

Notice, however, that there are not county boundaries for counties with no earthquakes. We can add these easily, however. The process is similar to creating a static map in ggplot2. We start by downloading the county boundaries from tigris and converting them to a simple features geometric object with a WGS84 coordinate system.

# download missouri data
moCounty <- counties(state = "MO", year = 2015)
Using FIPS code '29' for state 'MO'
# convert to simple feature
moCounty <- st_as_sf(moCounty)
# transform to WGS84
moCounty <- st_transform(moCounty, crs = 4326)

Once we have them transformed appropriately, we add the moCounty data as a layer under the countyQuakes data. We do not need to define popups for these (though we could), and since there is no additional interactivity we can also skip setting highlightOptions().

leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(data = moCounty, 
              color = "#444444", 
              weight = 1, 
              smoothFactor = 0.5,
              opacity = 1.0,
              fillColor = ""
              ) %>%
  addPolygons(data = countyQuakes, 
              color = "#444444", 
              weight = 1, 
              smoothFactor = 0.5,
              opacity = 1.0, 
              fillOpacity = 0.5,
              fillColor = ~binpal(COUNT),
              popup = ~county_popup, 
              highlightOptions = highlightOptions(
                color = "white", 
                weight = 2,
                bringToFront = TRUE)
              )

Notice that the colored polygons with earthquake data now appear duller. This is because they are not totally opaque and thus the gray layer below them bleeds through. This behavior can be eliminiated by transforming the moCounty data so that it is the compliment of the countyQuakes data - i.e. it contains only counties without earthquakes. We create a pipeline that:

  1. Take the countyQuakes data and then
  2. Remove the geometric reference data, then
  3. Joins these data with moCounty, then
  4. Removes observations that have valid counts, then
  5. Assigns the results to a new geometric object named countyNoQuakes
countyQuakes %>%
  `st_geometry<-`(NULL) %>%
  left_join(moCounty, ., by = "GEOID") %>%
  filter(is.na(COUNT) == TRUE) -> countyNoQuakes

We can take our newly created data and replace moCounty with it in the first addPolygons() function call. This will restore the brightness of the countyQuakes polygons:

leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(data = countyNoQuakes, 
              color = "#444444", 
              weight = 1, 
              smoothFactor = 0.5,
              opacity = 1.0,
              fillColor = ""
              ) %>%
  addPolygons(data = countyQuakes, 
              color = "#444444", 
              weight = 1, 
              smoothFactor = 0.5,
              opacity = 1.0, 
              fillOpacity = 0.5,
              fillColor = ~binpal(COUNT),
              popup = ~county_popup, 
              highlightOptions = highlightOptions(
                color = "white", 
                weight = 2,
                bringToFront = TRUE)
              )
LS0tCnRpdGxlOiAiSW50ZXJhY3RpdmUgTWFwcGluZyB3aXRoIGxlYWZsZXQiCmF1dGhvcjogIkNocmlzdG9waGVyIFByZW5lciwgUGguRC4iCmRhdGU6ICJKYW51YXJ5IDE0LCAyMDE3IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnRyb2R1Y3Rpb24KVGhpcyBub3RlYm9vayBkZXRhaWxzIHRoZSBjcmVhdGlvbiBvZiBpbnRlcmFjdGl2ZSBtYXBzIHVzaW5nIGBsZWFmdGxldGAgYW5kIGEgZGF0YSBzZXQgb2YgZWFydGhxdWFrZXMgaW4gTWlzc291cmkuIFdlIGNyZWF0ZWQgdGhlIHNoYXBlZmlsZXMgcmVmZXJlbmNlZCBoZXJlIGluIGBDcmVhdGVRdWFrZXMuUm1kYCBhbmQgYENvdW50eVF1YWtlcy5SbWRgLgoKIyMgUHJvamVjdCBTZXQgVXAKQmVmb3JlIHByb2NlZWRpbmcsIHdlJ2xsIG1ha2Ugc3VyZSBvdXIgbm90ZWJvb2sgaXMgc2V0IHVwIHRvIHdvcmsgd2l0aCBvdXIgcHJvamVjdCBkYXRhIHN0cnVjdHVyZS4KCmBgYHtyIHNldHVwfQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9IGhlcmU6OmhlcmUoKSkKYGBgCgpTZWUgYENyZWF0ZVF1YWtlcy5SbWRgIGZvciBhbiBleHBsYW5hdGlvbiBvZiBob3cgdGhpcyBmdW5jdGlvbiB3b3Jrcy4KCiMjIERlcGVuZGVuY2llcwpUaGUgZm9sbG93aW5nIHBhY2thZ2VzIGFyZSByZXF1aXJlZCBmb3IgdGhpcyBub3RlYm9vazoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKSAgICAgICAgIyBkYXRhIHdyYW5nbGluZwpsaWJyYXJ5KGdncGxvdDIpICAgICAgIyBwbG90dGluZwoKbGlicmFyeShza2ltcikgICAgICAgICMgZGVzY3JpcHRpdmUgc3RhdGlzdGljcwoKbGlicmFyeShzZikgICAgICAgICAgICMgc3BhdGlhbCBkYXRhIHRvb2wKbGlicmFyeSh0aWdyaXMpICAgICAgICMgZG93bmxvYWQgY2Vuc3VzIHNoYXBlZmlsZXMKbGlicmFyeShsZWFmbGV0KSAgICAgICMgaW50ZXJhY3RpdmUgbWFwcwpsaWJyYXJ5KGh0bWx0b29scykgICAgIyBodG1sIHRvb2xzIGZvciB1c2Ugd2l0aCBsZWFmbGV0IHBvcHVwcwoKbGlicmFyeShnZ3RoZW1lcykgICAgICMgZ2dwbG90MiB0aGVtZSBmb3IgbWFwcGluZwpgYGAKCiMjIExvYWQgRWFydGhxdWFrZSBEYXRhCldlJ2xsIHdhbnQgdG8gc3RhcnQgYnkgcmVhZGluZyB0aGUgZWFydGhxdWFrZSBkYXRhIHdlIGNyZWF0ZWQgcHJldmlvdXNseSBpbnRvIFIncyBnbG9iYWwgZW52aXJvbm1lbnQuIFdlIGNhbiB1c2UgdGhlIGByZWFkX3NmKClgIGZ1bmN0aW9uIHRvIGFjY29tcGxpc2ggdGhpczoKCmBgYHtyfQptb1F1YWtlcyA8LSByZWFkX3NmKCJyZXN1bHRzL0dFT19FYXJ0aHF1YWtlcy9HRU9fRWFydGhxdWFrZXMuc2hwIikKY291bnR5UXVha2VzIDwtIHJlYWRfc2YoInJlc3VsdHMvR0VPX0VhcnRocXVha2VzQnlDb3VudHkvR0VPX0VhcnRocXVha2VzQnlDb3VudHkuc2hwIikKYGBgCgpgbGVhZmxldGAgcmV0dXJucyB3YXJuaW5ncyBhYm91dCBkYXRhIHRoYXQgZG8gbm90IHVzZSB0aGUgW1dvcmxkIEdlb2RldGljIFN5c3RlbSdzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Xb3JsZF9HZW9kZXRpY19TeXN0ZW0pIDE5ODQgcmV2aXNpb24sIGtub3duIGFzIFdHUzg0LiBMaWtlIE5BRDE5ODMsIFdHUzg0IGlzIGEgZ2VvZ3JhcGhpYyBjb29yZGluYXRlIHN5c3RlbSBkZXNpZ25lZCBmb3Igd29ybGR3aWRlIGxvY2F0aW5nIG9mIGRhdGEuIFdlIGNhbiB0cmFuc2Zvcm0gYm90aCBvZiBvdXIgc2hhcGVmaWxlcywgd2hpY2ggd2VyZSBjcmVhdGVkIHVzaW5nIE5BRDE5ODM6CgpgYGB7cn0KbW9RdWFrZXMgPC0gc3RfdHJhbnNmb3JtKG1vUXVha2VzLCBjcnMgPSA0MzI2KQpjb3VudHlRdWFrZXMgPC0gc3RfdHJhbnNmb3JtKGNvdW50eVF1YWtlcywgY3JzID0gNDMyNikKYGBgCgojIyBTaW1wbGUgTGVhZmxldCBNYXAKV2l0aCBvdXIgdHJhbnNmb3JtZWQgZGF0YSwgd2UgY2FuIG1ha2UgYSBzaW1wbGUgYGxlYWZsZXRgIG1hcCBvZiBlYXJ0aHF1YWtlIGxvY2F0aW9ucyBpbiBNaXNzb3VyaS4gT3VyIHBpcGVsaW5lIHJlcXVpcmVzIHRocmVlIGVsZW1lbnRzOgoKMS4gV2UgbmVlZCB0byBjYWxsIHRoZSBgbGVhZmxldCgpYCBmdW5jdGluIGFuZCBkZWZpbmUgdGhlIGRhdGEgc291cmNlIGFzIGBtb1F1YWtlc2AsICoqdGhlbioqCjIuIFdlIG5lZWQgdG8gYWRkIG1hcCB0aWxlcyB1c2luZyBgYWRkVGlsZXMoKWAsICoqdGhlbioqCjMuIFdlIG5lZWQgdG8gcHJvamVjdCBvdXIgcG9pbnQgZGF0YSB1c2luZyBgYWRkTWFya2VycygpYAoKVGhlIHRpbGVzIHJlZmVyIHRvIHRoZSBiYXNlbWFwLCB3aGljaCBpcyBzb3VyY2VkIGZyb20gW09wZW5TdHJlZXRNYXBdKGh0dHA6Ly9vcGVuc3RyZWV0bWFwLm9yZykgYnkgZGVmYXVsdC4KCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBtb1F1YWtlcykgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRNYXJrZXJzKCkKYGBgCgojIyBDaGFuZ2luZyB0aGUgQmFzZW1hcApGb3IgbWFwcGluZyBkZXRhaWxlZCBkYXRhLCBJIGZpbmQgdGhlIE9wZW5TdHJlZXRNYXAgYmFzZW1hcCBoYXMgdG9vIG1hbnkgbGFiZWxzIGFuZCBtYXJrZXJzIG9uIGl0LiBJIHByZWZlciBhIHNpbXBsZXIgYmFzZW1hcCwgYW5kIGx1Y2tpbHkgYGxlYWZsZXRgIGhhcyB0aGUgYWJpbGl0eSB0byBwaWNrIGZyb20gZG96ZW5zIG9mIG9wdGlvbnMuIFdlIGNhbiBzcGVjaWZ5IG91ciBwcmVmZXJlbmNlIHVzaW5nIGBhZGRQcm92aWRlclRpbGVzKClgIGluc3RlYWQgb2YgYGFkZFRpbGVzKClgOgoKYGBge3J9CmxlYWZsZXQoZGF0YSA9IG1vUXVha2VzKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRNYXJrZXJzKCkKYGBgCgpZb3UgY2FuIGxlYXJuIG1vcmUgYWJvdXQgYmFzZW1hcHMgaW4gYGxlYWZsZXRgIFtoZXJlXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvYmFzZW1hcHMuaHRtbCksIGFuZCBzZWUgYW4gaW50ZXJhY3RpdmUgcHJldmlldyBvZiB0aGUgdmFyaW91cyBjaG9pY2VzIFtoZXJlXShodHRwOi8vbGVhZmxldC1leHRyYXMuZ2l0aHViLmlvL2xlYWZsZXQtcHJvdmlkZXJzL3ByZXZpZXcvaW5kZXguaHRtbCkuCgojIyBXb3JraW5nIHdpdGggUG9pbnQgRGF0YQpXZSdsbCB1c2UgdGhlIGVhcnRocXVha2UgZGF0YSB0byBpbnRyb2R1Y2UgYSBudW1iZXIgb2YgY29uY2VwdHMgZm9yIHdvcmtpbmcgd2l0aCBwb2ludCBkYXRhIHVzaW5nIGBsZWFmbGV0YC4KCiMjIyBBZGRpbmcgUG9wLXVwcwpPbmUgb2YgdGhlIGdyZWF0IHRoaW5ncyBhYm91dCBgbGVhZmxldGAgaXMgdGhhdCBwb3AtdXBzIGNhbiBiZSBhZGRlZCB0byBlYWNoIG1hcmtlciB0byBwcm92aWRlIGRldGFpbHMgYWJvdXQgdGhlIHBvaW50LiBGb3IgZXhhbXBsZSwgd2UgY291bGQgYWRkIGluZm9ybWF0aW9uIGFib3V0IHRoZSBkYXRlIGFuZCBtYWduaXR1ZGUgb2YgZWFjaCBlYXJ0aHF1YWtlLiBXZSdsbCBwYXN0ZSBhIHRlbXBsYXRlIHRvZ2V0aGVyIGludG8gYW4gb2JqZWN0IGluIG91ciBlbnZpb3JubWVudCB0aGF0IGBsZWFmbGV0YCBjYW4gdXNlIGZvciBlYWNoIHBvcC11cCBsaWtlIHNvOgoKYGBge3J9Cm1vUXVha2VzIDwtIG11dGF0ZShtb1F1YWtlcywgZGF0ZSA9IHN1YnN0cih0aW1lLCAxLCAxMCkpCgplYXJ0aHF1YWtlX3BvcHVwIDwtIHBhc3RlKCJEYXRlOiAiLCBtb1F1YWtlcyRkYXRlLCAgIjxici8+IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJNYWduaXR1ZGU6ICIsIG1vUXVha2VzJG1hZykKYGBgCgpUaGUgYDxici8+YCB0YWcgaXMgYGh0bWxgIHRoYXQgcGxhY2VzIGEgY2FycmlhZ2UgcmV0dXJuIGludG8gdGhlIHBvcC11cCB0ZXh0LiBUaGlzIHdpbGwgcGxhY2UgdGhlIGRhdGUgYW5kIHRoZSBtYWduaXR1ZGUgb24gZGlmZmVyZW50IGxpbmVzLgoKV2UgY2FuIGluc2VydCB0aGUgcG9wLXVwIGludG8gb3VyIG1hcCBieSB1c2luZyB0aGUgYHBvcHVwYCBhcmd1bWVudCBpbiBgYWRkTWFya2VycygpYCBhbmQgcHJlY2VkaW5nIGBlYXJ0aHF1YWtlX3BvcHVwYCB3aXRoIGEgdGlsZGUgKGB+YCkuIFdoZW4geW91IGV4ZWN1dGUgdGhlIGNodW5rIGJlbG93LCB0cnkgY2xpY2tpbmcgb24gZGlmZmVyZW50IG1hcmtlcnMgdG8gcmV2ZWFsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBlYXJ0aHF1YWtlcy4KCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBtb1F1YWtlcykgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkTWFya2Vycyhwb3B1cCA9IH5lYXJ0aHF1YWtlX3BvcHVwKQpgYGAKCiMjIyBDdXN0b21pemluZyBNYWtlcnMKYGxlYWZsZXRgIGhhcyBhIHdpZGUgdmFyaWV0eSBvZiB0b29scyBmb3IgY3VzdG9taXppbmcgbWFya2Vycy4gVGhlIGNvZGUgY2h1bmsgYmVsb3cgZGVmaW5lcyB0aHJlZSBwb2ludHMgYmFzZWQgb24gdGhlIG1hZ25pdHVkZSBvZiBhIGdpdmVuIGVhcnRocXVha2UuIElmIHRoZSBlYXJ0aHF1YWtlIGhhcyBhIG1hZ25pdHVkZSBvZiBsZXNzIHRoYW4gMywgYSBncmVlbiBtYXJrZXIgd2lsbCBiZSB1c2VkLiBJZiB0aGUgbWFnbml0dWRlIGlzIGJldHdlZW4gMyBhbmQgNCwgYW5kIG9yYW5nZSBtYXJrZXIgd2lsbCBiZSB1c2VkLiBGaW5hbGx5LCBmb3IgdGhlIGZldyBlYXJ0aHF1YWtlcyB3aXRoIG1hZ25pdHVkZXMgZ3JlYXRlciB0aGFuIDQsIHRoZSBtYXJrZXIgd2lsbCBiZSByZWQuIFRoZXNlIHBvaW50cyBhcmUgZGVmaW5lZCBpbnNpZGUgYSBmdW5jdGlvbiB0aGF0IHdlIGNhbiB1c2UgYXMgd2UgY29uc3RydWN0aW9uIG91ciBpY29ucy4KCmBgYHtyfQojIGRlZmluZSBmdW5jdGlvbiB0byBzZXQgYSBjb2xvciBiYXNlZCBvbiBtYWduaXR1ZGUKZ2V0Q29sb3IgPC0gZnVuY3Rpb24ocXVha2VDb2xvcikgewogIHNhcHBseShtb1F1YWtlcyRtYWcsIGZ1bmN0aW9uKG1hZykgewogICAgaWYobWFnIDwgMykgewogICAgICAiZ3JlZW4iCiAgICB9IGVsc2UgaWYobWFnIDwgNCkgewogICAgICAib3JhbmdlIgogICAgfSBlbHNlIHsKICAgICAgInJlZCIKICAgIH0gCiAgfSkKfQpgYGAKCkFmdGVyIGNyZWF0aW5nIGEgdG9vbCBmb3IgZGVmaW5pbmcgbWFya2VyIGNvbG9ycywgd2UgY2FuIHV0aWl6ZSBpdCBhcyBwYXJ0IG9mIHRoZSBgYXdlc29tZUljb25zKClgIGZ1bmN0aW9uIGNhbGwuIFdlJ2xsIHVzZSB0aGUgcGVyZmVjdGx5IG5hbWVkIFtGb250IEF3ZXNvbWVdKGh0dHA6Ly9mb250YXdlc29tZS5jb20pIGxpYnJhcnkgZm9yIHRoZSBtYXJrZXIgaW1hZ2UsIGFuZCBzZXQgdGhlIG1hcmtlciBjb2xvciBlcXVhbCB0byB0aGUgcmVzdWx0IG9mIHRoZSBgZ2V0Q29sb3IoKWAgZnVuY3Rpb24gd2UganVzdCB3cm90ZS4KCmBgYHtyfQojIGRlZmluZSBpY29uIHByb3BlcnRpZXMsIGluY2x1ZGluZyBzZXR0aW5nIG1hcmtlciBjb2xvciBiYXNlZCBvbiB0aGUgZnVuY3Rpb24gd2UganVzdCB3cml0ZQppY29ucyA8LSBhd2Vzb21lSWNvbnMoCiAgbGlicmFyeSA9ICdmYScsCiAgaWNvbiA9ICdtYXAtbWFya2VyLWFsdCcsCiAgbWFya2VyQ29sb3IgPSBnZXRDb2xvcihtb1F1YWtlcykKKQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgb3VyIGljb25zIGNvbnN0cnVjdGVkLCB3ZSdsbCBhcHBseSB0aGVtIHRvIGFuIHVwZGF0ZWQgdmVyc2lvbiBvZiBvdXIgbWFwIHVzaW5nIGBhZGRBd2Vzb21lTWFya2VycygpYCBpbiBsaWV1IG9mIGBhZGQgTWFya2VycygpYDoKCmBgYHtyfQojIGFwcGx5IGN1c3RvbSBpY29ucwpsZWFmbGV0KGRhdGEgPSBtb1F1YWtlcykgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQXdlc29tZU1hcmtlcnMocG9wdXAgPSB+ZWFydGhxdWFrZV9wb3B1cCwgaWNvbiA9IGljb25zKQpgYGAKCiMjIyBDbHVzdGVyaW5nIFBvaW50cwpUaGUgbWFwIGFib3ZlIGlzIGRpZmZpY3VsdCB0byByZWFkIGJlY2F1c2UgdGhlcmUgYXJlIHNvIG1hbnkgcG9pbnRzIGluIHNvdXRoZWFzdCBNaXNzb3VyaS4gT25lIHdheSB0byBoZWxwIHJlYWRlcnMgaXMgdG8gY2x1c3RlciBwb2ludHMuIFRoZSBgY2x1c3Rlck9wdGlvbnNgIGFyZ3VtZW50IGNhbiBiZSBpbmNsdWRlZCBpbiBgYWRkQXdlc29tZU1hcmtlcnMoKWAgYW5kIHNldCBlcXVhbCB0byBgbWFya2VyQ2x1c3Rlck9wdGlvbnMoKWAuIFRoaXMgd2lsbCBhbGxvdyBgbGVhZmxldGAgdG8gY2x1c3RlciBwb2ludHMgdG9nZXRoZXIgYmFzZWQgb24gcHJveGltZXR5LiBUaGUgY29sb3JzIHVzZWQgYnkgYGxlYWZsZXRgIGFyZSB2ZXJ5IGNsb3NlIHRvIG91ciBtYXJrZXIgY29sb3JzIGZyb20gYmVmb3JlLCB3aGljaCBpcyBjb25mdXNpbmcuIFRoZXkgZG8gbm90IGhhdmUgYW55dGhpbmcgdG8gZG8gd2l0aCB0aGUgbWFnbml0dWRlLCBob3dldmVyLiBJbnN0ZWFkLCB0aGV5IHJlZmxlY3QgdGhlIG51bWJlciBvZiBjaGlsZCBwb2ludHMgY2x1c3RlcmVkIHVuZGVyIGVhY2ggbWFya2VyIChncmVlbiBmb3IgZmV3LCB5ZWxsb3cgZm9yIGEgbW9kZXJhdGUgbnVtYmVyLCBhbmQgcmVkaXNoLW9yYW5nZSBmb3IgYSBsYXJnZSBudW1iZXIpLiBIb3ZlcmluZyB5b3VyIG1vdXNlIG92ZXIgYSBjdXN0ZXIgd2lsbCByZXZhbCB0aGUgYm91bmRhcmllcyBvZiB0aGUgaW5jbHVkZWQgY2hpbGQgcG9pbnRzLCBhbmQgY2xpY2tpbmcgb24gYSBjbHVzdGVyIHdpbGwgem9vbSB5b3UgaW4gdG8gdGhvc2UgYm91bmRhcmllcy4KCmBgYHtyfQojIGFwcGx5IGN1c3RvbSBpY29ucwpsZWFmbGV0KGRhdGEgPSBtb1F1YWtlcykgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgYWRkQXdlc29tZU1hcmtlcnMoCiAgICBwb3B1cCA9IH5lYXJ0aHF1YWtlX3BvcHVwLCAKICAgIGljb24gPSBpY29ucywgCiAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkKICApCmBgYAoKIyMgV29ya2luZyB3aXRoIFBvbHlnb24gRGF0YQpXZSBjYW4gYWxzbyB1c2UgYGxlYWZsZXRgIHRvIG1hcCBwb2x5Z29uIGRhdGEsIGxpa2Ugb3VyIGNvdW50IG9mIGVhcnRocXVha2VzIGJ5IGNvdW50eS4gV2UnbGwgc3RhcnQgYnkgZGVmaW5pbmcgcG9wLXVwIGNvbnRlbnQgZm9yIGNvdW50aWVzIHRoYXQgaGF2ZSBoYWQgZWFydGhxdWFrZXMuIFRoZSBwcm9jZXNzIGlzIHNpbWlsYXIgdG8gdGhlIHByb2Nlc3MgYWJvdmUgLSB3ZSdsbCBjb25jYXRlbmF0ZSB0ZXh0IChpbmNsdWRpbmcgdGhlIGA8YnIvPmAgaHRtbCB0YWcpIGFuZCBzYXZlIGl0IGluIGFuIG9iamVjdCBuYW1lZCBgY291bnR5X3BvcHVwYC4KCmBgYHtyfQpjb3VudHlfcG9wdXAgPC0gcGFzdGUoY291bnR5UXVha2VzJE5BTUVMU0FELCAgIjxici8+IiwgCiAgICAgICAgICAgICAgICAgICAgICAiRWFydGhxdWFrZXMgU2luY2UgMTk3MzogIiwgY291bnR5UXVha2VzJENPVU5UKQpgYGAKCldlJ2xsIGFsc28gZGVmaW5lIGEgY29sb3IgcmFtcCBmb3IgbWFwcGluZyBhbmQgc2F2ZSBpdCBpbiBhbiBvYmplY3QgY2FsbGVkIGBiaW5wYWxgLiBXZSdsbCB1c2UgdGhyZWUgYnJlYWtzIGFuZCB0aGUgYFJlZHNgIGNvbG9yIHJhbXAgZnJvbSBgUkNvbG9yQnJld2VyYCwganVzdCBsaWtlIHdlIGRpZCBpbiBvdXIgc3RhdGljIG1hcCBjcmVhdGVkIGluIGBDb3VudHlRdWFrZXMuUm1kYC4KCmBgYHtyfQpiaW5wYWwgPC0gY29sb3JCaW4oIlJlZHMiLCBjb3VudHlRdWFrZXMkQ09VTlQsIDMsIHByZXR0eSA9IEZBTFNFKQpgYGAKCk9uY2Ugd2UgaGF2ZSBwb3AtdXBzIGFuZCBhIGNvbG9yIHJhbXAgY3JlYXRlZCwgd2UnbGwgYXBwbHkgdGhlbSB0byBhIG1hcCB0aGF0IHVzZXMgdGhlIGBhZGRQb2x5Z29ucygpYCBmdW5jdGlvbiBpbiBsaWV1IG9mIGBhZGRNYXJrZXJzKClgLiBXaXRoaW4gdGhlIGBhZGRQb2x5Z29ucygpYCwgdGhlcmUgYXJlIGFyZ3VtZW50cyBmb3IgYm90aCB0aGUgc3Ryb2tlIGFuZCB0aGUgZmlsbCBvZiBlYWNoIHBvbHlnb24uIFdlIGFsc28gYWRkIGFuIGFkZGl0aW9uYWwgbGF5IG9mIGludGVyYWN0aXZpdHkgdGhhdCBoaWdobGlnaHRzIGVhY2ggc2VsZWN0ZWQgcG9seWdvbiBpbiB3aGl0ZS4KCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBjb3VudHlRdWFrZXMpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZFBvbHlnb25zKGNvbG9yID0gIiM0NDQ0NDQiLCAKICAgICAgICAgICAgICB3ZWlnaHQgPSAxLCAKICAgICAgICAgICAgICBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEuMCwgCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjUsCiAgICAgICAgICAgICAgZmlsbENvbG9yID0gfmJpbnBhbChDT1VOVCksCiAgICAgICAgICAgICAgcG9wdXAgPSB+Y291bnR5X3BvcHVwLCAKICAgICAgICAgICAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucygKICAgICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgCiAgICAgICAgICAgICAgICB3ZWlnaHQgPSAyLAogICAgICAgICAgICAgICAgYnJpbmdUb0Zyb250ID0gVFJVRSkKICAgICAgICAgICAgICApCmBgYAoKTm90aWNlLCBob3dldmVyLCB0aGF0IHRoZXJlIGFyZSBub3QgY291bnR5IGJvdW5kYXJpZXMgZm9yIGNvdW50aWVzIHdpdGggbm8gZWFydGhxdWFrZXMuIFdlIGNhbiBhZGQgdGhlc2UgZWFzaWx5LCBob3dldmVyLiBUaGUgcHJvY2VzcyBpcyBzaW1pbGFyIHRvIGNyZWF0aW5nIGEgc3RhdGljIG1hcCBpbiBgZ2dwbG90MmAuIFdlIHN0YXJ0IGJ5IGRvd25sb2FkaW5nIHRoZSBjb3VudHkgYm91bmRhcmllcyBmcm9tIGB0aWdyaXNgIGFuZCBjb252ZXJ0aW5nIHRoZW0gdG8gYSBzaW1wbGUgZmVhdHVyZXMgZ2VvbWV0cmljIG9iamVjdCB3aXRoIGEgV0dTODQgY29vcmRpbmF0ZSBzeXN0ZW0uCgpgYGB7cn0KIyBkb3dubG9hZCBtaXNzb3VyaSBkYXRhCm1vQ291bnR5IDwtIGNvdW50aWVzKHN0YXRlID0gIk1PIiwgeWVhciA9IDIwMTUpCgojIGNvbnZlcnQgdG8gc2ltcGxlIGZlYXR1cmUKbW9Db3VudHkgPC0gc3RfYXNfc2YobW9Db3VudHkpCgojIHRyYW5zZm9ybSB0byBXR1M4NAptb0NvdW50eSA8LSBzdF90cmFuc2Zvcm0obW9Db3VudHksIGNycyA9IDQzMjYpCmBgYAoKT25jZSB3ZSBoYXZlIHRoZW0gdHJhbnNmb3JtZWQgYXBwcm9wcmlhdGVseSwgd2UgYWRkIHRoZSBgbW9Db3VudHlgIGRhdGEgYXMgYSBsYXllciAqdW5kZXIqIHRoZSBgY291bnR5UXVha2VzYCBkYXRhLiBXZSBkbyBub3QgbmVlZCB0byBkZWZpbmUgcG9wdXBzIGZvciB0aGVzZSAodGhvdWdoIHdlIGNvdWxkKSwgYW5kIHNpbmNlIHRoZXJlIGlzIG5vIGFkZGl0aW9uYWwgaW50ZXJhY3Rpdml0eSB3ZSBjYW4gYWxzbyBza2lwIHNldHRpbmcgYGhpZ2hsaWdodE9wdGlvbnMoKWAuCgpgYGB7cn0KbGVhZmxldCgpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBtb0NvdW50eSwgCiAgICAgICAgICAgICAgY29sb3IgPSAiIzQ0NDQ0NCIsIAogICAgICAgICAgICAgIHdlaWdodCA9IDEsIAogICAgICAgICAgICAgIHNtb290aEZhY3RvciA9IDAuNSwKICAgICAgICAgICAgICBvcGFjaXR5ID0gMS4wLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9ICIiCiAgICAgICAgICAgICAgKSAlPiUKICBhZGRQb2x5Z29ucyhkYXRhID0gY291bnR5UXVha2VzLCAKICAgICAgICAgICAgICBjb2xvciA9ICIjNDQ0NDQ0IiwgCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMSwgCiAgICAgICAgICAgICAgc21vb3RoRmFjdG9yID0gMC41LAogICAgICAgICAgICAgIG9wYWNpdHkgPSAxLjAsIAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC41LAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5iaW5wYWwoQ09VTlQpLAogICAgICAgICAgICAgIHBvcHVwID0gfmNvdW50eV9wb3B1cCwgCiAgICAgICAgICAgICAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMoCiAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIAogICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpCiAgICAgICAgICAgICAgKQpgYGAKCk5vdGljZSB0aGF0IHRoZSBjb2xvcmVkIHBvbHlnb25zIHdpdGggZWFydGhxdWFrZSBkYXRhIG5vdyBhcHBlYXIgZHVsbGVyLiBUaGlzIGlzIGJlY2F1c2UgdGhleSBhcmUgbm90IHRvdGFsbHkgb3BhcXVlIGFuZCB0aHVzIHRoZSBncmF5IGxheWVyIGJlbG93IHRoZW0gYmxlZWRzIHRocm91Z2guIFRoaXMgYmVoYXZpb3IgY2FuIGJlIGVsaW1pbmlhdGVkIGJ5IHRyYW5zZm9ybWluZyB0aGUgYG1vQ291bnR5YCBkYXRhIHNvIHRoYXQgaXQgaXMgdGhlIGNvbXBsaW1lbnQgb2YgdGhlIGBjb3VudHlRdWFrZXNgIGRhdGEgLSBpLmUuIGl0IGNvbnRhaW5zIG9ubHkgY291bnRpZXMgd2l0aG91dCBlYXJ0aHF1YWtlcy4gV2UgY3JlYXRlIGEgcGlwZWxpbmUgdGhhdDoKCjEuIFRha2UgdGhlIGBjb3VudHlRdWFrZXNgIGRhdGEgYW5kICoqdGhlbioqCjIuIFJlbW92ZSB0aGUgZ2VvbWV0cmljIHJlZmVyZW5jZSBkYXRhLCAqKnRoZW4qKgozLiBKb2lucyB0aGVzZSBkYXRhIHdpdGggYG1vQ291bnR5YCwgKip0aGVuKioKNC4gUmVtb3ZlcyBvYnNlcnZhdGlvbnMgdGhhdCBoYXZlIHZhbGlkIGNvdW50cywgKip0aGVuKioKNS4gQXNzaWducyB0aGUgcmVzdWx0cyB0byBhIG5ldyBnZW9tZXRyaWMgb2JqZWN0IG5hbWVkIGBjb3VudHlOb1F1YWtlc2AKCmBgYHtyfQpjb3VudHlRdWFrZXMgJT4lCiAgYHN0X2dlb21ldHJ5PC1gKE5VTEwpICU+JQogIGxlZnRfam9pbihtb0NvdW50eSwgLiwgYnkgPSAiR0VPSUQiKSAlPiUKICBmaWx0ZXIoaXMubmEoQ09VTlQpID09IFRSVUUpIC0+IGNvdW50eU5vUXVha2VzCmBgYAoKV2UgY2FuIHRha2Ugb3VyIG5ld2x5IGNyZWF0ZWQgZGF0YSBhbmQgcmVwbGFjZSBgbW9Db3VudHlgIHdpdGggaXQgaW4gdGhlIGZpcnN0IGBhZGRQb2x5Z29ucygpYCBmdW5jdGlvbiBjYWxsLiBUaGlzIHdpbGwgcmVzdG9yZSB0aGUgYnJpZ2h0bmVzcyBvZiB0aGUgYGNvdW50eVF1YWtlc2AgcG9seWdvbnM6CgpgYGB7cn0KbGVhZmxldCgpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBjb3VudHlOb1F1YWtlcywgCiAgICAgICAgICAgICAgY29sb3IgPSAiIzQ0NDQ0NCIsIAogICAgICAgICAgICAgIHdlaWdodCA9IDEsIAogICAgICAgICAgICAgIHNtb290aEZhY3RvciA9IDAuNSwKICAgICAgICAgICAgICBvcGFjaXR5ID0gMS4wLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9ICIiCiAgICAgICAgICAgICAgKSAlPiUKICBhZGRQb2x5Z29ucyhkYXRhID0gY291bnR5UXVha2VzLCAKICAgICAgICAgICAgICBjb2xvciA9ICIjNDQ0NDQ0IiwgCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMSwgCiAgICAgICAgICAgICAgc21vb3RoRmFjdG9yID0gMC41LAogICAgICAgICAgICAgIG9wYWNpdHkgPSAxLjAsIAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC41LAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5iaW5wYWwoQ09VTlQpLAogICAgICAgICAgICAgIHBvcHVwID0gfmNvdW50eV9wb3B1cCwgCiAgICAgICAgICAgICAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMoCiAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIAogICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpCiAgICAgICAgICAgICAgKQpgYGAKCg==